#pragma once

#include <unordered_map>
#include <zenox/minilog.hpp>
#include <string>
#include <thread>
#include <any>

namespace zenox {

struct SubprogramRegistry {
public:
    typedef int (*subprogram_main_t)(int argc, char **argv);

private:
    std::unordered_map<std::string, subprogram_main_t> m_table;

    subprogram_main_t get(std::string const &key) {
        auto it = m_table.find(key);
        if (it != m_table.end()) [[likely]] {
            return it->second;
        }
        return nullptr;
    }

    bool define(std::string key, subprogram_main_t fp) {
        auto [it, ok] = m_table.emplace(std::move(key), std::move(fp));
        if (!ok) [[unlikely]] {
            zenox::log_warn("subprogram redefinition: {}", it->first);
        }
        return ok;
    }

    static SubprogramRegistry &instance();

public:
    static int call(std::string const &key, int argc, char **argv) {
        auto fp = instance().get(key);
        if (!fp) [[unlikely]] {
            zenox::log_error("subprogram not found: {}", key);
            return -1;
        }
        return fp(argc, argv);
    }

    struct Define {
        explicit Define(std::string key, subprogram_main_t fp) {
            instance().define(std::move(key), std::move(fp));
        }
    };
};

#define ZENO_SUBPROGRAM(name) \
    static int _subprogram_##name(int, char **); \
    zenox::SubprogramRegistry::Define _defsubprogram_##name(#name, _subprogram_##name); \
    static int _subprogram_##name

struct SubroutineRegistry {
public:
    typedef void (*subroutine_main_t)(std::stop_token stop, std::any arg);

private:
    std::unordered_map<std::string, subroutine_main_t> m_table;

    subroutine_main_t get(std::string const &key) {
        auto it = m_table.find(key);
        if (it != m_table.end()) [[likely]] {
            return it->second;
        }
        return nullptr;
    }

    bool define(std::string key, subroutine_main_t fp) {
        auto [it, ok] = m_table.emplace(std::move(key), std::move(fp));
        if (!ok) [[unlikely]] {
            zenox::log_warn("subroutine redefinition: {}", it->first);
        }
        return ok;
    }

    static SubroutineRegistry &instance();

public:
    static std::jthread call(std::string const &key, std::any arg = std::any()) {
        auto fp = instance().get(key);
        if (!fp) [[unlikely]] {
            zenox::fatal_error("subroutine not found: {}", key);
        }
        return std::jthread([fp = std::move(fp), arg = std::move(arg)] (std::stop_token stop) mutable {
            fp(std::move(stop), std::move(arg));
        });
    }

    struct Define {
        explicit Define(std::string key, subroutine_main_t fp) {
            instance().define(std::move(key), std::move(fp));
        }
    };
};

#define ZENO_SUBROUTINE(name) \
    static void _subroutine_##name(std::stop_token, std::any); \
    zenox::SubroutineRegistry::Define _defsubroutine_##name(#name, _subroutine_##name); \
    static void _subroutine_##name

}
